從開始拿到程式後,花了五天在調整程式,目的就是為了今天!
CI 的原文是 Continuous Integration ,也就是要「常常做整合」。如果整合能夠自動化,我們甚至也可以把任務交付給 CI server 處理。新的專案可以一開始就考慮自動化,但 legacy code 通常會因為自動化不完整,而讓接 CI server 不是那麼容易。
前五天最大的目的就是要讓整合的過程盡可能自動化,這樣我們才能讓 legacy code 接 CI server 變得比較容易一點。
因程式碼是放在 GitHub Public Repo ,因此決定選擇 Travis CI 。
簡單的串接方法可以參考「開源專案的好選擇 -- Travis CI」,本文章就不多介紹,直接給 .travis.yml
:
sudo: false
os: linux
dist: trusty
language: php
php: 7.1
branches:
only:
- example
services:
- mysql
env:
global:
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: shopcart
DB_USERNAME: root
DB_PASSWORD: password
before_install:
- mysql -e "CREATE DATABASE ${DB_DATABASE} DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;"
- mysql -e "SET PASSWORD = PASSWORD('${DB_PASSWORD}');"
install:
- composer install
before_script:
- cp .env.example .env
- php artisan key:generate
- php artisan migrate --force
script:
- php vendor/bin/phpunit
cache:
directories:
- vendor
順帶一提, Laravel 專案大概都可以用這樣的 template 作為串 Travis CI 的開始。
但 CI 這樣會回報錯誤,原因是因為下面這行出錯了:
$ php vendor/bin/phpunit
拿到本機跑也是會出錯,而這也是我們做 CI 所期望的--CI 錯, Local 就要有一樣的錯,反之亦然。
它的錯誤大致上是因為 config.php
有三個不能在 PHPUnit 跑的問題:
session_start()
不能在輸出後才執行,不過事實上 CLI 跑 session 總是很多問題,拿掉還是比較簡單的選擇$_SERVER
最好不要使用,不過程式並沒有使用到 ROOT_URL
,所以直接移除吧define()
被重覆定義一樣的常數,因為 PHPUnit 會重覆載入 config.php
導致有這樣的問題。使用 defined
即可順利解決:
defined('DB_TYPE') or define('DB_TYPE', 'mysql');
defined('DB_CHARSET') or define('DB_CHARSET', 'utf8');
defined('DB_HOST') or define('DB_HOST', '127.0.0.1');
defined('DB_USER') or define('DB_USER', 'root');
defined('DB_PASS') or define('DB_PASS', 'password');
defined('DB_NAME') or define('DB_NAME', 'shopcart');
上面問題解決完之後,測試可以通過了,但是會把 HTML 也輸出。這是因為 require index.php
時,會直接把 output echo 出來,這要用 ob_start()
與 ob_get_clean()
解決:
Route::get('/', function () {
ob_start();
require_once __DIR__ . '/../index.php';
return ob_get_clean();
});
到目前為止,就可以讓 CI 正常測試了。
現在可以比較放心的改程式了,因為 CI 會把關一部分的功能。首先設定檔有個問題是, Laravel 吃的資料庫連線設定與 config.php 的設定是不一致的,這容易發生一邊改,另一邊忘了改的問題。因此我們必須要重構調整設計。
首先 DB_TYPE
是多餘的,因此我們可以把它刪掉。再來,進到 config.php 時,已經可以使用 config()
,所以我們可以這樣取得 MySQL 的連線資訊:
defined('DB_CHARSET') or define('DB_CHARSET', config('database.connections.mysql.charset'));
defined('DB_HOST') or define('DB_HOST', config('database.connections.mysql.host'));
defined('DB_USER') or define('DB_USER', config('database.connections.mysql.username'));
defined('DB_PASS') or define('DB_PASS', config('database.connections.mysql.password'));
defined('DB_NAME') or define('DB_NAME', config('database.connections.mysql.database'));
改完後,記得跑一下測試會不會過:
$ php vendor/bin/phpunit
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.
.... 4 / 4 (100%)
Time: 220 ms, Memory: 14.00MB
OK (4 tests, 4 assertions)
過了,過完之後就 commit 與 push 吧!讓 CI 也測試一下。
再來 DEBUG_MODE
也可以使用 env 載入:
define('DEBUG_MODE', env('APP_DEBUG'));
改完一樣測試、 commit 、 push 。
當有了自動化測試與 CI server 之後,重構就會變得非常簡單!
上面可以看到,流程是這樣的:
測試或是 CI 驗證失敗時,我們都可以得知第一手消息,並立即修正。
對大部分的 legacy code 而言,因為通常沒有單元測試,執行測試通常很困難。但我們可以試著在最外層加上驗收測試,至少確保功能行為是正常的。
明天就來談談我們該如何追加驗收測試。